package com.nowcoder.community.service;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.nowcoder.community.dao.DiscussPostMapper;
import com.nowcoder.community.entity.DiscussPost;
import com.nowcoder.community.util.SensitiveFilter;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.util.HtmlUtils;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author xiexu
 * @create 2022-06-02 17:21
 */
@Service
public class DiscussPostService {

    private static final Logger logger = LoggerFactory.getLogger(DiscussPostService.class);

    @Autowired
    private DiscussPostMapper discussPostMapper;

    @Autowired
    private SensitiveFilter sensitiveFilter;

    @Value("${caffeine.posts.max-size}")
    private int maxSize;

    @Value("${caffeine.posts.expire-seconds}")
    private int expireSeconds;

    // Caffeine核心接口：Cache，LoadingCac(同步缓存)，AsyncLoadingCache(异步缓存)

    // 帖子列表的缓存
    private LoadingCache<String, List<DiscussPost>> postListCache;

    // 帖子总数的缓存
    private LoadingCache<Integer, Integer> postRowsCache;

    /**
     * 被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行，并且只会被服务器执行一次
     */
    @PostConstruct
    public void init() {
        // 初始化帖子列表缓存
        postListCache = Caffeine.newBuilder()
                // 最大缓存容量
                .maximumSize(maxSize)
                // 缓存过期时间
                .expireAfterWrite(expireSeconds, TimeUnit.SECONDS)
                // 如果缓存中没有数据，需要指明怎么从数据库中得到数据并写入缓存的办法
                .build(new CacheLoader<String, List<DiscussPost>>() {
                    @Override
                    public @Nullable List<DiscussPost> load(String key) throws Exception {
                        if (key == null && key.length() == 0) {
                            throw new IllegalArgumentException("参数错误！");
                        }
                        // 将key拆分成两个参数
                        String[] params = key.split(":");
                        // 参数一定不为空，并且一定是两个参数，否则抛出异常
                        if (params == null && params.length != 2) {
                            throw new IllegalArgumentException("参数错误！");
                        }

                        int offset = Integer.valueOf(params[0]);
                        int limit = Integer.valueOf(params[1]);

                        // 扩展：可以自己再加一个二级缓存 Redis -> Mysql

                        // 从数据库查数据，获取后将数据放入本地缓存
                        logger.debug("load post list from DB...");
                        return discussPostMapper.selectDiscussPosts(0, offset, limit, 1);
                    }
                });

        // 初始化帖子总数缓存
        postRowsCache = Caffeine.newBuilder()
                // 最大缓存容量
                .maximumSize(maxSize)
                // 缓存过期时间
                .expireAfterWrite(expireSeconds, TimeUnit.SECONDS)
                // 如果缓存中没有数据，需要指明怎么从数据库中得到数据并写入缓存的办法
                .build(new CacheLoader<Integer, Integer>() {
                    @Override
                    public @Nullable Integer load(Integer key) throws Exception {
                        // 从数据库查数据，获取后将数据放入本地缓存
                        logger.debug("load post rows from DB...");
                        return discussPostMapper.selectDiscussPostRows(key);
                    }
                });
    }

    /**
     * 查询帖子列表
     *
     * @param userId    等于0表示首页查询
     * @param offset
     * @param limit
     * @param orderMode 等于1表示查询热门帖子
     * @return
     */
    public List<DiscussPost> findDiscussPosts(int userId, int offset, int limit, int orderMode) {
        // 当访问首页并且是热门帖子列表的时候，才从缓存里取出结果
        if (userId == 0 && orderMode == 1) {
            return postListCache.get(offset + ":" + limit);
        }
        // 其他数据都是直接从数据库中取结果
        logger.debug("load post list from DB...");
        return discussPostMapper.selectDiscussPosts(userId, offset, limit, orderMode);
    }

    /**
     * 查询帖子列表总行数
     *
     * @param userId 等于0表示首页查询总行数
     * @return
     */
    public int findDiscussPostRows(int userId) {
        // 当访问的是首页查询的时候，才从缓存中取出结果
        if (userId == 0) {
            return postRowsCache.get(userId);
        }
        // 其他数据都是直接从数据库中取结果
        logger.debug("load post rows from DB...");
        return discussPostMapper.selectDiscussPostRows(userId);
    }

    /**
     * 插入帖子
     *
     * @param post
     * @return
     */
    public int addDiscussPost(DiscussPost post) {
        if (post == null) {
            throw new IllegalArgumentException("参数不能为空！");
        }
        // 转义HTML标记，如果标题中含有类似于<script>xxx</script>，会将标题转成普通文本而不会破坏网页显示内容
        post.setTitle(HtmlUtils.htmlEscape(post.getTitle()));
        post.setContent(HtmlUtils.htmlEscape(post.getContent()));
        // 过滤敏感词
        post.setTitle(sensitiveFilter.filter(post.getTitle()));
        post.setContent(sensitiveFilter.filter(post.getContent()));
        post.setStatus(0);
        post.setType(0);
        return discussPostMapper.insertDiscussPost(post);
    }

    /**
     * 根据id查询帖子
     *
     * @param id
     * @return
     */
    public DiscussPost findDiscusspostById(int id) {
        return discussPostMapper.selectDiscussPostById(id);
    }

    /**
     * 更新帖子评论数量
     *
     * @param id
     * @param commentCount
     * @return
     */
    public int updateCommentCount(int id, int commentCount) {
        return discussPostMapper.updateCommentCount(id, commentCount);
    }

    /**
     * 根据id修改帖子的类型
     *
     * @param id   帖子id
     * @param type
     * @return
     */
    public int updateType(int id, int type) {
        return discussPostMapper.updateType(id, type);
    }

    /**
     * 根据id修改帖子的状态
     *
     * @param id     帖子id
     * @param status
     * @return
     */
    public int updateStatus(int id, int status) {
        return discussPostMapper.updateStatus(id, status);
    }

    /**
     * 更新帖子的分数
     *
     * @param id
     * @param score
     * @return
     */
    public int updateScore(int id, double score) {
        return discussPostMapper.updateScore(id, score);
    }

}
